/**
 * 
 */
package com.rpg.base.flyweight;

import java.util.ArrayList;

import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.dalonedrau.watchable.Watchable;
import com.dalonedrau.watchable.Watcher;
import com.rpg.base.interactive.flyweight.BaseInteractiveObject;

/**
 * @author Donald
 */
public abstract class IOPCData implements Watchable {
	/**
	 * the reference ids of all items equipped by the {@link IOPCData}, indexed
	 * by equipment slot.
	 */
	private int[]				equippedItems;
	/** any flags being applied to the {@link IOPCData}. */
	private long				flags		= 0;
	/** the {@link IOPCData}'s gender. */
	private int					gender		= -1;
	/** the character's gold. */
	private float				gold;
	/** interface flags. */
	private int					interfaceFlags;
	/** the player's key ring. */
	private String[]			keyring;
	/** the {@link IOPCData}'s level. */
	private int					level		= 0;
	/** the {@link IOPCData}'s name. */
	private char[]				name;
	/** the number of keys on the key ring. */
	private int					numKeys;
	/** the {@link IOPCData}'s Profession. */
	private int					profession	= -1;
	/** the {@link IOPCData}'s Race. */
	private int					race		= -1;
	/**
	 * the list of {@link Watcher}s associated with this {@link IOPCData}.
	 */
	private ArrayList<Watcher>	watchers	= new ArrayList<Watcher>();
	/** the {@link IOPCData}'s experience points. */
	private long				xp;
	/** Creates a new instance of {@link IOPCData}. */
	protected IOPCData() { }
	/**
	 * Adds an interface flag.
	 * @param flag the flag
	 */
	public final void addInterfaceFlag(final long flag) {
		interfaceFlags |= flag;
	}
	/**
	 * Adds a key to the keyring.
	 * @param key the key
	 */
	public final void addKey(final String key) {
		if (keyring == null) {
			keyring = new String[0];
		}
		int index = -1;
		for (int i = 0; i < keyring.length; i++) {
			if (keyring[i] == null) {
				index = i;
				break;
			}
			if (keyring[i].equalsIgnoreCase(key)) {
				index = i;
				break;
			}
		}
		if (index == -1) {
			String[] dest = new String[keyring.length + 1];
			System.arraycopy(keyring, 0, dest, 0, keyring.length);
			keyring = dest;
			dest = null;
		}
		keyring[index] = key;
		numKeys++;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void addWatcher(final Watcher watcher) {
		watchers.add(watcher);
	}
	/**
	 * Adjusts the {@link IOPCData}'s experience points.
	 * @param val the amount adjusted by
	 */
	public final void adjustXp(final long val) {
		xp += val;
	}
	/** Clears all flags that were set. */
	public final void clearFlags() {
		flags = 0;
	}
	/** Clears all interface flags that were set. */
	public final void clearInterfaceFlags() {
		interfaceFlags = 0;
	}
	/** Clears all ability score modifiers. */
	public abstract void clearModAbilityScores();
	/**
	 * Gets the reference id of the item the {@link IOPCData} has equipped at a
	 * specific equipment slot. -1 is returned if no item is equipped.
	 * @param slot the equipment slot
	 * @return <code>int</code>
	 * @throws Exception if the equipment slot was never defined
	 */
	public final int getEquippedItem(final int slot) throws Exception {
		int id = -1;
		if (slot < 0
				|| slot >= equippedItems.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - equipment slot ");
			sb.append(slot);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		id = equippedItems[slot];
		return id;
	}
	/**
	 * Gets the {@link IOPCData}'s gender.
	 * @return int
	 */
	public final int getGender() {
		return gender;
	}
	/**
	 * Gets the character's gold.
	 * @return <code>float</code>
	 */
	public final float getGold() {
		return gold;
	}
	public final String getKey(final int index) {
		String key = null;
		if (keyring != null) {
			key = keyring[index];
		}
		return key;
	}
	/**
	 * Gets the key ring.
	 * @return {@link String[]}
	 */
	public String[] getKeyring() {
		return keyring;
	}
	/**
	 * Gets the {@link IOPCData}'s level.
	 * @return int
	 */
	public final int getLevel() {
		return level;
	}
	/**
	 * Gets the {@link IOPCData}'s name.
	 * @return char[]
	 */
	public final char[] getName() {
		return name;
	}
	/**
	 * Gets the numKeys
	 * @return {@link int}
	 */
	public int getNumKeys() {
		return numKeys;
	}
	/**
	 * Gets the {@link IOPCData}'s Profession.
	 * @return int
	 */
	public final int getProfession() {
		return profession;
	}
	/**
	 * Gets the {@link IOPCData}'s Race.
	 * @return int
	 */
	public final int getRace() {
		return race;
	}
	/**
	 * Gets the {@link IOPCData}'s experience points.
	 * @return int
	 */
	public final long getXp() {
		return xp;
	}
	/**
	 * Determines if the {@link IOPCData} has a specific flag.
	 * @param flag the flag
	 * @return true if the {@link IOPCData} has the flag; false otherwise
	 */
	public final boolean hasFlag(final long flag) {
		return (flags & flag) == flag;
	}
	/**
	 * Determines if the {@link IOPCData} has a specific flag.
	 * @param flag the flag
	 * @return true if the {@link IOPCData} has the flag; false otherwise
	 */
	public boolean hasInterfaceFlag(final long flag) {
		return (interfaceFlags & flag) == flag;
	}
	/**
	 * Initializes the items the {@link IOPCData} has equipped.
	 * @param total the total number of equipment slots
	 */
	protected final void initEquippedItems(final int total) {
		equippedItems = new int[total];
		for (int i = 0; i < equippedItems.length; i++) {
			equippedItems[i] = -1;
		}
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void notifyWatchers() {
		for (int i = 0; i < watchers.size(); i++) {
			watchers.get(i).watchUpdated(this);
		}
	}
	/**
	 * Removes a flag.
	 * @param flag the flag
	 */
	public final void removeFlag(final long flag) {
		flags &= ~flag;
	}
	/**
	 * Removes an interface flag.
	 * @param flag the flag
	 */
	public void removeInteraceFlag(final long flag) {
		interfaceFlags &= ~flag;
	}
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void removeWatcher(final Watcher watcher) {
		watchers.remove(watcher);
	}
	/**
	 * Sets the reference id of the item the {@link IOPCData} has equipped at a
	 * specific equipment slot.
	 * @param slot the equipment slot
	 * @param item the item being equipped
	 * @throws Exception if the equipment slot was never defined
	 */
	public final void setEquippedItem(final int slot,
			final BaseInteractiveObject item) throws Exception {
		if (slot < 0
				|| slot >= equippedItems.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - equipment slot ");
			sb.append(slot);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		equippedItems[slot] = item.getRefId();
	}
	/**
	 * Sets the reference id of the item the {@link IOPCData} has equipped at a
	 * specific equipment slot.
	 * @param slot the equipment slot
	 * @param id the item's reference id
	 * @throws Exception if the equipment slot was never defined
	 */
	public final void setEquippedItem(final int slot, final int id)
			throws Exception {
		if (slot < 0
				|| slot >= equippedItems.length) {
			PooledStringBuilder sb =
					StringBuilderPool.getInstance().getStringBuilder();
			sb.append("Error - equipment slot ");
			sb.append(slot);
			sb.append(" is outside array bounds.");
			Exception ex = new Exception(sb.toString());
			sb.returnToPool();
			throw ex;
		}
		equippedItems[slot] = id;
	}
	/**
	 * Sets a flag.
	 * @param flag the flag
	 */
	public final void setFlag(final long flag) {
		flags |= flag;
	}
	/**
	 * Sets the {@link IOPCData}'s gender.
	 * @param val the gender to set
	 */
	public final void setGender(final int val) {
		gender = val;
		notifyWatchers();
	}
	/**
	 * Sets the character's gold.
	 * @param val the gold to set
	 */
	public final void setGold(final float val) {
		gold = val;
	}
	/**
	 * Sets the key ring.
	 * @param val the keyring to set
	 */
	public final void setKeyring(final String[] val) {
		keyring = val;
	}
	/**
	 * Sets the {@link IOPCData}'s level.
	 * @param val the level to set
	 */
	public final void setLevel(final int val) {
		level = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link IOPCData}'s name.
	 * @param val the name to set
	 */
	public final void setName(final char[] val) {
		name = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link IOPCData}'s name.
	 * @param val the name to set
	 */
	public final void setName(final String val) {
		name = val.toCharArray();
		notifyWatchers();
	}
	/**
	 * Sets the numKeys
	 * @param numKeys the numKeys to set
	 */
	public void setNumKeys(int numKeys) {
		this.numKeys = numKeys;
	}
	/**
	 * Sets the {@link IOPCData}'s Profession.
	 * @param val the profession to set
	 */
	public final void setProfession(final int val) {
		profession = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link IOPCData}'s Race.
	 * @param val the race to set
	 */
	public final void setRace(final int val) {
		race = val;
		notifyWatchers();
	}
	/**
	 * Sets the {@link IOPCData}'s experience points.
	 * @param val the amount to set
	 */
	public final void setXp(final long val) {
		xp = val;
		notifyWatchers();
	}
}
